package com.cloud.common.context;

import com.alibaba.csp.sentinel.context.ContextUtil;
import com.cloud.common.annotation.AuthInner;
import com.cloud.common.annotation.WebInterceptor;
import com.cloud.common.bean.ResultInfo;
import com.cloud.common.bean.SessionInfo;
import com.cloud.common.config.WebConfig;
import com.cloud.common.util.JsonUtils;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
@WebInterceptor(order = Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnMissingClass("com.cloud.gateway.bean.GatewayInfo")
public class ContextInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
            return true;
        }
        SessionInfo sessionInfo = null;
        String sessionStr = request.getHeader(SessionContext.SESSION_INFO);
        if (StringUtils.isNotEmpty(sessionStr)) {
            sessionInfo = JsonUtils.parseObject(sessionStr, SessionInfo.class);
            SessionContext.set(sessionInfo);
            if (handler instanceof HandlerMethod) {
                // 检查授权
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                if (!checkAuth(request, response, handlerMethod)) {
                    return false;
                }
            }
        } else if (!AppContext.isProdEnv()) {
            // 方便没有登录的情况下本地调试接口
            sessionInfo = SessionInfo.buildDefault();
            SessionContext.set(sessionInfo);
        }

        // 创建跟踪号
        String traceId = request.getHeader(TraceContext.TRACE_ID);
        if (StringUtils.isEmpty(traceId)) {
            // 附带用户ID方便跟踪问题
            String userId = sessionInfo == null ? "" : sessionInfo.getUserId();
            traceId = TraceContext.create(userId);
        }
        TraceContext.set(traceId);
        RequestContext.set(request);
        RequestContext.maskData(true);

        String versionInfo = request.getHeader(VersionContext.VERSION_INFO);
        if (StringUtils.isNotEmpty(versionInfo)) {
            VersionContext.set(versionInfo);
        }
        String xid = request.getHeader(RootContext.KEY_XID);
        if (StringUtils.isNotEmpty(xid)) {
            RootContext.bind(xid);
        }

        String fromGateway = request.getHeader(WebConfig.FROM_GATEWAY);
        fromGateway = StringUtils.isNotEmpty(fromGateway) ? fromGateway : Boolean.FALSE.toString();
        if (!AppContext.isProdEnv()) {
            log.info("requestURI >>> {}, sessionInfo >>> {}, fromGateway >>> {}", request.getRequestURI(), sessionInfo, fromGateway);
        } else {
            log.debug("requestURI >>> {}, sessionInfo >>> {}, fromGateway >>> {}", request.getRequestURI(), sessionInfo, fromGateway);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) {
        response.setHeader(HttpHeaders.PRAGMA, "no-cache");
        response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
        response.setDateHeader(HttpHeaders.EXPIRES, 0);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        TraceContext.remove();
        SessionContext.remove();
        VersionContext.remove();
        RequestContext.remove();
        ContextUtil.exit();
    }

    private boolean checkAuth(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws IOException {
        // 从网关进来不可访问只授权内部的接口
        String fromGateway = request.getHeader(WebConfig.FROM_GATEWAY);
        if (StringUtils.isNotEmpty(fromGateway) && fromGateway.equals(Boolean.TRUE.toString())) {
            AuthInner authInner = handlerMethod.getMethod().getAnnotation(AuthInner.class);
            if (authInner != null) {
                ResultInfo<String> resultInfo = ResultInfo.failure().setCode(HttpStatus.UNAUTHORIZED.value());
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.getWriter().println(JsonUtils.toJSONString(resultInfo));
                return false;
            }
        }
        return true;
    }
}
